package com.packt.mqttessentials.Sensors02;

import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.ThreadLocalRandom;

public class SensorsManager implements MqttCallback {
	private static final String SENSOR_EARTH_HUMIDITY = "earthhumidity";
	private static final String SENSOR_SUNLIGHT = "sunlight";
	private static final String TOPIC_SEPARATOR = "/";
	private final String boardCommandsTopic;
	private final String boardDataBaseTopic;
	private final String encoding;
	private final MqttAsyncClient asyncClient;
	private final String earthHumidityTopic;
	private final String visibleLightTopic;
	private final String infraredLightTopic;
	private final String ultraVioletIndexTopic;
	private volatile boolean isSunlightSensorTurnedOn = false;
	private volatile boolean isEarthHumiditySensorTurnedOn = false;
	
	public SensorsManager(final MqttAsyncClient asyncClient, 
			final String boardCommandsTopic, final String boardDataBaseTopic, final String encoding) {
		this.boardCommandsTopic = boardCommandsTopic;
		this.boardDataBaseTopic = boardDataBaseTopic;
		this.encoding = encoding;
		this.asyncClient = asyncClient;
		// Build and save the topic names that we will use to publish the data from the sensors
		this.earthHumidityTopic = this.boardDataBaseTopic.concat(SENSOR_EARTH_HUMIDITY);
		final String sunlightDataBaseTopic = boardDataBaseTopic.concat(SENSOR_SUNLIGHT);
		this.visibleLightTopic = String.join(TOPIC_SEPARATOR, sunlightDataBaseTopic, "visiblelight");
		this.infraredLightTopic = String.join(TOPIC_SEPARATOR, sunlightDataBaseTopic, "ir");
		this.ultraVioletIndexTopic = String.join(TOPIC_SEPARATOR, sunlightDataBaseTopic, "uv");
	}

	public IMqttDeliveryToken publishMessage(final String topic, 
		final String textForMessage, IMqttActionListener actionListener,
		final int qos, final boolean retained) {
		byte[] bytesForPayload;
		try {
			bytesForPayload = textForMessage.getBytes(this.encoding);
			return asyncClient.publish(topic, bytesForPayload, qos, 
				retained, null, actionListener);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		} catch (MqttException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public void publishProcessedCommandMessage(final String sensorName, final String command) {
		final String topic = String.format("%s/%s", boardCommandsTopic, sensorName);
		final String textForMessage = String.format(
			"%s successfully processed command: %s", sensorName, command);
		publishMessage(topic, textForMessage, null, 0, false);
	}
	
	@Override
	public void connectionLost(Throwable cause) {
		// The MQTT client lost the connection
		cause.printStackTrace();
	}

	@Override
	public void deliveryComplete(IMqttDeliveryToken token) {
		// Delivery for a message has been completed
		// and all acknowledgments have been received
	}

	@Override
	public void messageArrived(String topic, MqttMessage message) throws Exception {
		String messageText = new String(message.getPayload(), encoding);
		System.out.println(
			String.format("Topic: %s. Payload: %s",
				topic,
				messageText));
		// A message has arrived from the MQTT broker
		// The MQTT broker doesn't send back 
		// an acknowledgment to the server until 
		// this method returns cleanly
		if (!topic.startsWith(boardCommandsTopic)) {
			// The topic for the arrived message doesn't start with boardTopic
			return;
		}
		final boolean isTurnOnMessage = messageText.equals("TURN ON"); 
		final boolean isTurnOffMessage = messageText.equals("TURN OFF");
		boolean isInvalidCommand = false;
		boolean isInvalidTopic = false;
		// Extract the sensor name from the topic
		String sensorName = topic.replaceFirst(boardCommandsTopic, "").replaceFirst(TOPIC_SEPARATOR, "");
		switch (sensorName) {
			case SENSOR_SUNLIGHT:
				if (isTurnOnMessage) {
					isSunlightSensorTurnedOn = true;
				} else if (isTurnOffMessage) {
					isSunlightSensorTurnedOn = false;
				} else {
					isInvalidCommand = true;
				}
				break;
			case SENSOR_EARTH_HUMIDITY:
				if (isTurnOnMessage) {
					isEarthHumiditySensorTurnedOn = true;
				} else if (isTurnOffMessage) {
					isEarthHumiditySensorTurnedOn = false;
				} else {
					isInvalidCommand = true;
				}
				break;
			default:
				isInvalidTopic = true;
		}
		if (!isInvalidCommand && !isInvalidTopic) {
			publishProcessedCommandMessage(sensorName, messageText);	
		}
	}
	
	public void loop() {
		if (isEarthHumiditySensorTurnedOn) {
			// Retrieve the humidity level from the sensor
			// In this case, we just generate a random number
			final int humidityLevel = ThreadLocalRandom.current().nextInt(1, 101);
			// Publish the message to the appropriate topic
			publishMessage(earthHumidityTopic, 
				String.format("%d %%", humidityLevel), null, 0, false);
		}
		if (isSunlightSensorTurnedOn) {
			// Retrieve the visible light level from the sensor
			// In this case, we just generate a random number
			final int visibleLight = ThreadLocalRandom.current().nextInt(201, 301);
			// Publish the message to the appropriate topic
			publishMessage(visibleLightTopic,
				String.format("%d lm", visibleLight), null, 0, false);

			// Retrieve the infrared light level from the sensor
			// In this case, we just generate a random number
			final int infraredLight = ThreadLocalRandom.current().nextInt(251, 281);
			// Publish the message to the appropriate topic
			publishMessage(infraredLightTopic,
				String.format("%d lm", infraredLight), null, 0, false);

			// Retrieve the ultraviolet (UV) index from the sensor
			// In this case, we just generate a random number
			final int ultraVioletIndex = ThreadLocalRandom.current().nextInt(0, 16);
			// Publish the message to the appropriate topic
			publishMessage(ultraVioletIndexTopic,
				String.format("%d UV Index", ultraVioletIndex), null, 0, false);
		}
	}
}